﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using NHibernate;

namespace Apollo.Modules.Salary
{
    public class SalaryModule : IModule
    {
        public const string ACTION_SAP_EXPORT_ACCOUNTING = "SAP_EXPORT_ACCOUNTING";
        public const string ACTION_PAYROLL_CALCULATION = "PAYROLL_CALCULATION";
        public const string ACTION_REP_PAYMENT_INDIVIDUAL = "REP_PAYMENT_INDIVIDUAL";
        public const string ACTION_CLOSE_MONTH = "CLOSE_MONTH";

        public const int UI_ACCOUNTING_ROLE_ID = 351;

        public void OnInit(Application app) {
            app.RegisterAction(ACTION_SAP_EXPORT_ACCOUNTING);
            app.RegisterAction(ACTION_PAYROLL_CALCULATION);
            app.RegisterAction(ACTION_REP_PAYMENT_INDIVIDUAL);
            app.RegisterAction(ACTION_CLOSE_MONTH);
        }

        public bool IsAllowed(string actionName, Entity entity, object obj)
        {
            // do not allow modifications / payroll calculations for closed months
            if (obj is AccountingMonth) {
                if (actionName == ACTION_SAP_EXPORT_ACCOUNTING)
                    return true;

                return Apollo.Application.ACTION_VIEW_NAME == actionName;
            }

            if (obj is Transaction && obj != null && actionName != Apollo.Application.ACTION_VIEW_NAME) {
                if ((obj as Transaction).AccountingMonth == null || (obj as Transaction).AccountingMonth.IsClosed)
                    return false;
            }

            if (obj is TransactionArchive || obj is PayrollArchive || entity.Name == "TransactionArchive"
                || entity.Name == "PayrollArchive") {
                    return (actionName == Apollo.Application.ACTION_VIEW_NAME);
            }

            if (obj is Employee && Apollo.Application.CurrentUser.Role.Id == UI_ACCOUNTING_ROLE_ID)
                return (actionName == Apollo.Application.ACTION_VIEW_NAME);

            if (obj == null) {
                if (entity.Name == "AccountingMonth")
                    return Apollo.Application.ACTION_VIEW_NAME == actionName
                        || Apollo.Application.ACTION_EXTRACOLUMNS == actionName
                        || (ACTION_CLOSE_MONTH == actionName && Apollo.Application.CurrentUser.Role.Id == UI_ACCOUNTING_ROLE_ID);
            }

            return true;
        }

        public ActionSubscription PrepareAction(string actionName, object obj)
        {
            if (obj != null && (obj is AccountingMonth || obj is Transaction)) {
                if (actionName == Apollo.Application.ACTION_SAVE_NAME || actionName == Apollo.Application.ACTION_DELETE_NAME)
                    return new ActionSubscription(this, true, ActionHandlingType.ActiveButAllowingPassive, 1);
            }

            return null;
        }

        public EditActionSubscription PrepareEditAction() {
            int entityId = 0;

            if (!int.TryParse(Apollo.Application.Current.Request["EntityId"], out entityId))
                return null;

            Entity entity = Apollo.Application.MetaEntity(entityId);

            if (entity.Name == "Payroll")
            {
                EditActionSubscription subscription =  new EditActionSubscription(this, true, ActionHandlingType.Active, ActionSubscription.DEFAULT_PRIORITY);

                subscription.Entity = entity;
                subscription.Edit = new EntityEdit()
                {
                    EditString = @"
<form entityname=""Payroll"" entityid=""91"">
  <group type=""Properties"" displayname=""Payroll"">
    <property col=""0"" row=""0"" type=""Normal"">BaseIncomeValue</property>
    <property col=""1"" row=""0"" type=""Normal"">GrossIncomeValue</property>
    <property col=""2"" row=""0"" type=""Normal"">NetIncomeValue</property>
    <property col=""0"" row=""1"" type=""Normal"">PaidValue</property>
    <property col=""1"" row=""1"" type=""ReadOnly"">InsertDate</property>
    <property col=""2"" row=""1"" type=""ReadOnly"">InsertBy</property>
    <property col=""0"" row=""2"" type=""ReadOnly"">ModifiedDate</property>
    <property col=""1"" row=""2"" type=""ReadOnly"">ModifiedBy</property>
    <property col=""2"" row=""2"" type=""Normal"">Employee</property>
    <property col=""0"" row=""3"" type=""Normal"">AccountingMonth</property>
    <property col=""1"" row=""3"" type=""ReadOnly"">Id</property>
    <property col=""2"" row=""3"" type=""Normal"">CostCenter</property>
    <property col=""0"" row=""4"" type=""Normal"">Role</property>
    <property col=""1"" row=""4"" type=""Normal"">Position</property>
    <property col=""0"" row=""8"" type=""Normal"">Code</property>
    <property col=""1"" row=""8"" type=""Normal"">InternalCode</property>
  </group>
</form>
",
                    Entity = entity
                };

                return subscription;

            }

            return null;
        }

        public ActionContext Save(object obj)
        {
            if (obj is AccountingMonth)
                return this.AssertAccountingMonth(obj as AccountingMonth);
            else if (obj is Transaction)
                return this.AssertTransaction(obj as Transaction);

            return null;
        }

        public ActionContext Delete(object obj)
        {
            if (obj is AccountingMonth)
                return this.AssertAccountingMonth(obj as AccountingMonth);
            else if (obj is Transaction)
                return this.AssertTransaction(obj as Transaction);

            return null;
        }

        public ActionContext DoAction(string actionName, object obj)
        {
            return null;
        }

        protected ActionContext AssertAccountingMonth(AccountingMonth month)
        {
            if (month == null)
                return null;

            if (month.IsClosed == true && (bool)Apollo.Application.Current.GetOriginalEntityProperty(month, "IsClosed") == true)
                return new ActionContext(new ActionMessage(month.Name + " is locked", ActionMessageType.Error)) { State = ActionState.Failed };

            // check if there is another open month
            if (Apollo.Application.CurrentSession.QueryOver<AccountingMonth>()
                .Where(m => m.IsClosed == false).RowCount() > 0) {
                return new ActionContext(new ActionMessage("There is already an open month. You must close it before opening another one!", ActionMessageType.Error)) { State = ActionState.Failed };
            }

            ActionContext context = new ActionContext() { State = ActionState.Succesful };
            
            if (month.Id <= 0) 
                context.OnActionSuccess += new ActionCompletionHandler(AccountingMonthCreate_OnActionSuccess);

            return context;
        }


        protected void AccountingMonthCreate_OnActionSuccess(object obj)
        {
            ITransaction tran = Apollo.Application.CurrentSession.BeginTransaction();
            try
            {
                AccountingMonth month = obj as AccountingMonth;

                Entity en = Apollo.Application.CurrentSession.QueryOver<Entity>()
                    .Where(e => e.Name == "Transaction").SingleOrDefault();

                // on creation
                EntityProperty ep = Apollo.Application.CurrentSession.QueryOver<EntityProperty>()
                    .Where(e => e.Entity.Id == en.Id && e.PropertyName == "AccountingMonth")
                    .SingleOrDefault();
                ep.DefaultValue = month.Id.ToString();

                Apollo.Application.CurrentSession.Save(ep);

                tran.Commit();

                Apollo.Application.ClearCache();
            }
            catch {
                tran.Rollback();
            }
        }


        protected ActionContext AssertTransaction(Transaction tran)
        {
            if (tran == null)
                return null;

            if (tran.AccountingMonth == null)
                return new ActionContext(new ActionMessage("No accounting month found!", ActionMessageType.Error)) { State = ActionState.Failed };

            if (tran.Employee == null)
                return new ActionContext(new ActionMessage("No employee found!", ActionMessageType.Error)) { State = ActionState.Failed };

            if (tran.Definition == null)
                return new ActionContext(new ActionMessage("No definition found!", ActionMessageType.Error)) { State = ActionState.Failed };

            AccountingMonth month = Apollo.Application.CurrentSession.Get<AccountingMonth>(tran.AccountingMonth.Id);

            if (month.IsClosed)
                return new ActionContext(new ActionMessage(month.Name + " is locked", ActionMessageType.Error)) { State = ActionState.Failed };

            if (tran.Id <= 0)
            {
                // on creation
                if (string.IsNullOrEmpty(tran.Name))
                {
                    TransactionDefinition tdefinition = Apollo.Application.CurrentSession.Get<TransactionDefinition>(tran.Definition.Id);
                    tran.Name = tdefinition.Name;
                }

                if (string.IsNullOrEmpty(tran.CostCenter))
                {
                    Employee employee = Apollo.Application.CurrentSession.Get<Employee>(tran.Employee.Id);
                    tran.CostCenter = employee.Position.CostCenter;
                }
            }

            return new ActionContext() { State = ActionState.Succesful };
        }



        [Action(ACTION_SAP_EXPORT_ACCOUNTING, ActionHandlingType.Active)]
        public static ActionContext RedirectToSAPExport(object obj) {
            AccountingMonth accountingMonth = obj as AccountingMonth;
            if (accountingMonth != null)
            {
                HttpContext.Current.Response.Redirect("/SAPExportAccounting.ashx?AccountingMonthId=" + accountingMonth.Id.ToString());
                HttpContext.Current.Response.End();
                return null;
            }
            else
                return new ActionContext(new ActionMessage("SAP exports are available only for accounting months!", ActionMessageType.Error));
        }

        [Action(ACTION_REP_PAYMENT_INDIVIDUAL, ActionHandlingType.Active)]
        public static ActionContext RedirectToReport_PaymentIndividual(object obj) {
            Payroll payroll = obj as Payroll;

            if (payroll == null)
            {
                return new ActionContext(new ActionMessage("This report is available only for payroll!", ActionMessageType.Error)) { State = ActionState.Failed };
            }
            else
            {
                string reportUrl = "http://10.237.9.237/ReportServer_OXY2011?/Apollo.Reports/PayrollIndividual&rc:Parameters=false&ID_Employee="
                    + payroll.Employee.Id.ToString()
                    + "&ID_AccountingMonth=" + payroll.AccountingMonth.Id.ToString();
                HttpContext.Current.Response.Redirect(reportUrl);
                HttpContext.Current.Response.End();
                return null;
            }
        }

        [Action(ACTION_PAYROLL_CALCULATION, ActionHandlingType.Active)]
        public static ActionContext PayrollCalculation(object obj) {
            // run the stored procedure that calculates the payrolls
            AccountingMonth accountingMonth = obj as AccountingMonth;
            
            #warning Change this part to support all dbs

            SqlConnection conn = Apollo.Application.CurrentSession.Connection as SqlConnection;
            SqlCommand cmd = new SqlCommand("dbo.sp_payroll_calculation", conn);
            Apollo.Application.CurrentSession.Transaction.Enlist(cmd);
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter err_no = new SqlParameter("@err_no", SqlDbType.Int) { Direction = ParameterDirection.Output };
            SqlParameter err_msg = new SqlParameter("@err_msg", SqlDbType.NVarChar, 300) { Direction = ParameterDirection.Output };

            if (accountingMonth != null)
                cmd.Parameters.Add(new SqlParameter("@ID_AccountingMonth", accountingMonth.Id));
            else
                cmd.Parameters.Add(new SqlParameter("@ID_AccountingMonth", DBNull.Value));
            cmd.Parameters.Add(err_no);
            cmd.Parameters.Add(err_msg);

            cmd.ExecuteNonQuery();

            if ((int)err_no.Value == 0)
                return new ActionContext(new ActionMessage("Calculation successfull", ActionMessageType.Info));
            else
                return new ActionContext(new ActionMessage((string)err_msg.Value, ActionMessageType.Warning));

        }

        [Action(ACTION_CLOSE_MONTH, ActionHandlingType.Active)]
        public static ActionContext CloseMonth(object obj)
        {
            // run the stored procedure that calculates the payrolls
            AccountingMonth accountingMonth = obj as AccountingMonth;

            #warning Change this part to support all dbs

            SqlConnection conn = Apollo.Application.CurrentSession.Connection as SqlConnection;
            SqlCommand cmd = new SqlCommand("dbo.sp_close_month", conn);
            Apollo.Application.CurrentSession.Transaction.Enlist(cmd);
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter err_no = new SqlParameter("@err_no", SqlDbType.Int) { Direction = ParameterDirection.Output };
            SqlParameter err_msg = new SqlParameter("@err_msg", SqlDbType.NVarChar, 300) { Direction = ParameterDirection.Output };

            if (accountingMonth != null)
                cmd.Parameters.Add(new SqlParameter("@ID_AccountingMonth", accountingMonth.Id));
            else
                cmd.Parameters.Add(new SqlParameter("@ID_AccountingMonth", DBNull.Value));
            cmd.Parameters.Add(err_no);
            cmd.Parameters.Add(err_msg);

            cmd.ExecuteNonQuery();

            if ((int)err_no.Value == 0)
                return new ActionContext(new ActionMessage((string)err_msg.Value, ActionMessageType.Info));
            else
                return new ActionContext(new ActionMessage((string)err_msg.Value, ActionMessageType.Warning));

        }

    }
}
